package cn.vendpay.com.service.impl;

import cn.vendcommon.com.entity.constants.CommonCodeMsg;
import cn.vendcommon.com.entity.constants.MQConstant;
import cn.vendcommon.com.entity.*;
import cn.vendcommon.com.entity.dto.DeductionAccountDTO;
import cn.vendcommon.com.entity.vo.UserVO;
import cn.vendcommon.com.exception.VendException;
import cn.vendcommon.com.utils.UserContext;
import cn.vendpay.com.client.SystemClient;
import cn.vendpay.com.client.YoloClient;
import cn.vendpay.com.mapper.VideoMapper;
import cn.vendpay.com.mapper.YOLORecognitionLogMapper;
import cn.vendpay.com.service.DeductionRecordService;
import cn.vendpay.com.service.VideoService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.RemoveObjectArgs;
import io.minio.errors.*;
import io.minio.http.Method;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


@Slf4j
@Service
public class VideoServiceImpl implements VideoService {

    private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(4);

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    private MinioClient minioClient;

    @Value("${minio.bucket}")
    private String bucketName;

    @Value("${minio.endpoint}")
    private String endpoint;

    @Value("${minio.expiration-time:3600}")
    private int expirationTime;

    @Autowired
    private YoloClient yoloClient;

    @Autowired
    private SystemClient systemClient;

    @Autowired
    private DeductionRecordService deductionRecordService;

    @Autowired
    private YOLORecognitionLogMapper yoloRecognitionLogMapper;

    @Autowired
    private VideoMapper videoMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;


    /**
     * 处理视频+更新视频状态（测试用）
     * @param machineId 售卖机ID
     * @param time 视频时长（秒）
     */
    @Override
    public void videoStartTest(int machineId, int time) {
        // 获取用户的基本信息
        Long userId = UserContext.getUserId();
        if (userId == null) {
            throw new VendException(CommonCodeMsg.ILLEGAL_OPERATION);
        }

        try {
            CompletableFuture<UserVO> userF = CompletableFuture.supplyAsync(() -> {
                Result<UserVO> r = systemClient.userInfoById(userId);
                if (r == null || r.getData() == null) {
                    throw new VendException(CommonCodeMsg.USER_NOT_FOUND);
                }
                return r.getData();
            }, THREAD_POOL);

            CompletableFuture<Map<String, Integer>> yoloF = CompletableFuture.supplyAsync(() -> {
                try {
                    log.info("<<< -------- 🚀视频开始处理 -------- >>>");
                    Map<String, Integer> intelligentMap = new HashMap<>();
                    // TODO 假数据测试使用
//                intelligentMap = yoloClient.startScan(time);
                    intelligentMap.put("红牛", 1);
                    intelligentMap.put("冰红茶", 2);
                    log.info("<<< -------- 视频处理结束🚀 -------- >>>");
                    return intelligentMap;
                } catch (Exception e) {
                    log.error("<<< -------- 视频处理失败🚀:{} -------- >>>", e.getMessage(), e);
                    throw new VendException(CommonCodeMsg.SCAN_ERROR);  // 扫描异常
                }
            }, THREAD_POOL);


            CompletableFuture<List<InventoryItem>> invF = CompletableFuture.supplyAsync(() -> {
                Result<List<InventoryItem>> r = systemClient.getInventoryByMachineId(machineId);
                if (r == null || r.getData() == null) throw new VendException(CommonCodeMsg.MACHINE_NOT_FOUND);
                return r.getData();
            }, THREAD_POOL);


            // 等待所有完成，任一失败时抛出异常
            CompletableFuture.allOf(userF, yoloF, invF).join();
            UserVO userVO = userF.join();
            Map<String, Integer> intelligentMap = yoloF.join();
            List<InventoryItem> inventoryItems = invF.join();

            Double[] sum = new Double[1];
            sum[0] = 0.0;
            // 遍历集合
            intelligentMap.forEach((k, v) -> {
                int flag = 0;
                log.info("<<< -------- 识别到的商品: {} 数量: {} -------- >>>", k, v);
                // 遍历商品列表，找到匹配的商品
                for (InventoryItem item : inventoryItems) {
                    if (item.getName().equals(k)) {
                        log.info("找到匹配的商品: {} 数量: {}", item.getName(), v);
                        if (item.getCount() < v) {
                            log.error("商品 {} 数量不足，当前数量: {}, 识别数量: {}", item.getName(), item.getCount(), v);
                            insertYOLORecognitionLogAsync(0, machineId, userVO, intelligentMap);
                            // TODO 提示管理员核实扣费数据
                            throw new VendException(CommonCodeMsg.RECOGNITION_ERROR);  // 商品数量不足
                        }
                        // 保存扣减数量
                        sum[0] += v * item.getPrice();
                        flag = 1;
                        break;
                    }
                }
                if (flag == 0) {
                    //未找到商品，记录日志并失败
                    insertYOLORecognitionLogAsync(0, machineId, userVO, intelligentMap);
                    throw new VendException(CommonCodeMsg.RECOGNITION_ERROR); // 或自定义错误：商品未匹配
                }
            });

            // 添加YOLO识别日志
            insertYOLORecognitionLogAsync(1, machineId, userVO, intelligentMap);

            String inventoryJson = objectMapper.writeValueAsString(intelligentMap);
            // 创建支付订单
            DeductionRecord deductionRecord = DeductionRecord.builder()
                    .nickname(userVO.getNickname())
                    .phone(userVO.getPhone())
                    .machineId(machineId)
                    .deductionItems(inventoryJson)
                    .lumpSum(sum[0])
                    .build();

            // TODO 支付模块
            Boolean status = deductionAmount(userId, sum[0]);

            // 支付成功判断
            if (status) {
                deductionRecord.setStatus(1);
                deductionRecord.setDeductionTime(LocalDateTime.now());
                deductionRecordService.insertDeductionRecord(deductionRecord);
            } else {
                deductionRecord.setStatus(0);
                deductionRecordService.insertDeductionRecord(deductionRecord);
                // TODO 扣款失败处理逻辑
                throw new VendException(CommonCodeMsg.DEDUCTION_FAILED);
            }

            systemClient.deductionInventoryByMachineId(machineId, intelligentMap);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 视频处理+支付+扣减库存
     * @param url 视频URL
     * @param machineId 售卖机ID
     * @param i 视频时长（秒）
     * @return
     */
    @Override
//    @GlobalTransactional(rollbackFor = Exception.class, timeoutMills = 30000)
    public boolean videoStartTestByUrl(int userId, String url, int machineId, int i) {
        System.out.println("seata全局事务id====================>"+ RootContext.getXID());
        String requestId = UUID.randomUUID().toString();
        try {
            // 获取个人信息
            UserVO userVO = Optional.ofNullable(systemClient.userInfoById((long) userId))
                    .map(Result::getData)
                    .orElseThrow(() -> new VendException(CommonCodeMsg.USER_NOT_FOUND));

            CompletableFuture<Map<String, Integer>> yoloF = CompletableFuture.supplyAsync(() -> processYOLO(machineId, userVO), THREAD_POOL);
            CompletableFuture<List<InventoryItem>> invF = CompletableFuture.supplyAsync(() -> loadInventory(machineId), THREAD_POOL);

            // 等待所有完成，任一失败时抛出异常
            CompletableFuture.allOf(yoloF, invF).join();
            Map<String, Integer> intelligentMap = yoloF.join();
            List<InventoryItem> inventoryItems = invF.join();

            Double[] sum = new Double[1];
            sum[0] = 0.0;
            // 遍历集合
            intelligentMap.forEach((k, v) -> {
                int flag = 0;
                log.info("<<< -------- 识别到的商品: {} 数量: {} -------- >>>", k, v);
                // 遍历商品列表，找到匹配的商品
                InventoryItem matched = inventoryItems.stream().filter(item -> item.getName().equals(k))
                        .findFirst().orElse(null);
                if (matched == null) {
                    insertYOLORecognitionLogAsync(0, machineId, userVO, intelligentMap);
                    throw new VendException(CommonCodeMsg.RECOGNITION_ERROR); // 未匹配到商品
                }

                if (matched.getCount() < v) {
                    insertYOLORecognitionLogAsync(0, machineId, userVO, intelligentMap);
                    throw new VendException(CommonCodeMsg.RECOGNITION_ERROR); // 商品不足
                }

                sum[0] += v * matched.getPrice();
            });

            // 添加YOLO识别日志
            insertYOLORecognitionLogAsync(1, machineId, userVO, intelligentMap);

            String inventoryJson = objectMapper.writeValueAsString(intelligentMap);
            // 创建支付订单
            DeductionRecord deductionRecord = DeductionRecord.builder()
                    .nickname(userVO.getNickname())
                    .phone(userVO.getPhone())
                    .deductionTime(LocalDateTime.now())
                    .deductionItems(inventoryJson)
                    .lumpSum(sum[0])
                    .videoUrl(url)
                    .build();

            // TODO 支付模块
            Boolean status = deductionAmount((long) userId, sum[0]);



            // 支付成功判断
            if (status) {
                deductionRecord.setStatus(1);
                deductionRecord.setDeductionTime(LocalDateTime.now());
                deductionRecordService.insertDeductionRecord(deductionRecord);
            } else {
                deductionRecord.setStatus(0);
                deductionRecordService.insertDeductionRecord(deductionRecord);
                // TODO 扣款失败处理逻辑
                throw new VendException(CommonCodeMsg.DEDUCTION_FAILED);
            }

            // 远程调用
//            systemClient.deductionInventoryByMachineId(machineId, intelligentMap);

            // <优化> 扣减库存，将扣减库存使用rabbitmq进行处理
            Map<String, Object> payMap = new HashMap<>();
            payMap.put("machineId", machineId);
            payMap.put("intelligentMap", intelligentMap);
            payMap.put("xid", RootContext.getXID());
            payMap.put("requestId", requestId);  // 唯一标识
            rabbitTemplate.convertAndSend(MQConstant.INVENTORY_EXCHANGE, MQConstant.INVENTORY_ROUTING_KEY, payMap);


            return true;
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 添加YOLO识别日志
     * @param status
     * @param machineId
     * @param userVO
     * @param intelligentMap
     */
    private void insertYOLORecognitionLog(int status, int machineId, UserVO userVO, Map<String, Integer> intelligentMap) throws JsonProcessingException {
        try {
            String inventoryJson = objectMapper.writeValueAsString(intelligentMap);
            YOLORecognitionLog yoloRecognitionLog = YOLORecognitionLog.builder()
                    .phone(userVO.getPhone())
                    .machineId(machineId)
                    .status(status)
                    .createTime(LocalDateTime.now())
                    .inventoryMap(inventoryJson)
                    .build();
            yoloRecognitionLogMapper.insert(yoloRecognitionLog);
        } catch (Exception e) {
            log.error("插入YOLO识别日志失败", e);
        }
    }

    /**
     * 异步写日志
     * @param status
     * @param machineId
     * @param userVO
     * @param intelligentMap
     */
    private void insertYOLORecognitionLogAsync(int status, int machineId, UserVO userVO, Map<String, Integer> intelligentMap) {
        CompletableFuture.runAsync(() -> {
            try {
                insertYOLORecognitionLog(status, machineId, userVO, intelligentMap);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }, THREAD_POOL);
    }


    /**
     * 用户扣款逻辑
     * @param aDouble 金额
     * @return 扣款结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Boolean deductionAmount(Long userId, Double aDouble) {
        DeductionAccountDTO dto = DeductionAccountDTO.builder()
                .id(userId)
                .account(aDouble)
                .build();
        Result result = systemClient.deductionUserAmount(dto);
        return result.getCode() == 0;
    }

    /**
     * 保存视频信息
     * @param videoItem
     */
    public void insertVideoItem(VideoItem videoItem) {
        videoMapper.insertVideo(videoItem);
    }

    /**
     * 根据状态取到前limitNum个视频信息
     * @param status
     * @param limitNum
     * @return
     */
    public List<VideoItem> getVideoItemsByStatusLimitNum(int status, int limitNum) {
        return videoMapper.getVideoItemsByStatusLimitNum(status, limitNum);
    }

    /**
     * 更新视频状态
     * @param videoItems 视频列表
     */
    @Override
    public void updateStatus(List<VideoItem> videoItems) {
        videoMapper.updateStatus(videoItems);
    }

    /**
     * 根据状态取到视频信息
     * @param status 视频状态
     * @return 视频列表
     */
    @Override
    public List<VideoItem> getVideoItemsByStatus(int status) {
        return videoMapper.getVideoItemsByStatus(status);
    }

    /**
     * 根据视频ID播放视频
     * @param videoId 视频ID
     * @return 视频URL
     */
    @Override
    public String playVideoByVideoId(int videoId) {
        String url = videoMapper.getUrlByVideoId(videoId);
        return getPresignedUrl(url);
    }

    /**
     * 根据视频ID删除视频
     * @param videoId 视频ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteByVideoId(int videoId) {
        Integer status = videoMapper.getStatusByVideoId(videoId);
        if (status == null) {
            throw new VendException(CommonCodeMsg.VIDEO_NOT_FOUND);
        } else if (status == 0) {
            throw new VendException(CommonCodeMsg.VIDEO_STATUS_ERROR);
        }
        // 标记删除
        int updated = videoMapper.markDeleted(videoId);
        if (updated == 0) {
            throw new VendException(CommonCodeMsg.VIDEO_NOT_FOUND);
        }
    }

    /**
     * 生成预签名URL
     * @param objectName 对象名称
     * @return 预签名URL
     */
    public String getPresignedUrl(String objectName) {
        try {
            return minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(objectName)
                            .expiry(expirationTime) // 有效期1小时
                            .build()
            );
        } catch (Exception e) {
            throw new RuntimeException("生成临时URL失败", e);
        }
    }


    /**
     * YOLO 识别
     * @param machineId 售卖机ID
     * @param userVO 用户信息
     * @return 识别结果
     */
    private Map<String, Integer> processYOLO(int machineId, UserVO userVO) {
        try {
            log.info("<<< -------- 🚀视频开始处理 -------- >>>");
            Map<String, Integer> intelligentMap = new HashMap<>();
            // TODO 假数据测试使用
            // intelligentMap = yoloClient.startScan(time);
            intelligentMap.put("红牛", 1);
            log.info("<<< -------- 视频处理结束🚀 -------- >>>");
            return intelligentMap;
        } catch (Exception e) {
            insertYOLORecognitionLogAsync(0, machineId, userVO, null);
            throw new VendException(CommonCodeMsg.SCAN_ERROR);
        }
    }

    /**
     * 获取库存
     * @param machineId 售卖机ID
     * @return 库存列表
     */
    private List<InventoryItem> loadInventory(int machineId) {
        Result<List<InventoryItem>> r = systemClient.getInventoryByMachineId(machineId);
        if (r == null || r.getData() == null) {
            throw new VendException(CommonCodeMsg.MACHINE_NOT_FOUND);
        }
        return r.getData();
    }

}
